package com.rightrule.rule.component;

import com.alibaba.fastjson.JSONObject;
import com.rightrule.entity.DecisionStrategyDivisionEntity;
import com.rightrule.entity.DecisionStrategyVersionDeployEntity;
import com.rightrule.rule.constans.*;
import com.rightrule.rule.dto.*;
import com.rightrule.rule.executor.RuleFlow;
import com.rightrule.rule.executor.node.*;
import com.rightrule.rule.executor.rule.ExpressionRule;
import com.rightrule.rule.executor.rule.ScriptRule;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
@Data
public class LoadPackageSnapshot {
    private RuleFlow ruleFlowCache;

    /**
     * 在线执行分流配置
     */
    private Map<String, StrategyDivisionSnapshotDTO> onlineDivisionCacheLogic = new HashMap<>();


    /**
     * 旁路测试分流配置
     */
    private Map<String, StrategyDivisionSnapshotDTO> onlineBypathDivisionCacheLogic = new HashMap<>();


    /**
     * 离线测试包
     */
    private Map<String, RuleFlow> offlineSnapshotCacheLogic = new HashMap<>();


    /**
     * 在线包
     */
    private Map<String, RuleFlow> onlineSnapshotCacheLogic = new HashMap<>();


    /**
     * 初始化所有的包配置，机器宕机重启的时候
     */
    public void initAllPackage() {
        Random random = new Random();
        Integer randomNum = (random.nextInt(10)) * 10;

        try {
            Thread.sleep(randomNum);
        } catch (InterruptedException e) {
            log.error("sleep error,num", randomNum);
        }

        initAllOnLinePackageSnapshot(null);
        initAllOfflinePackageSnapshot();
    }

    /**
     * 定时初始化所有的包版本，防止逻辑以外的初始化失败导致包未初始化成功 todo 定时任务捞取执行
     */
    public void scheduleAllOnlinePackage() {
        Random random = new Random();
        Integer randomNum = (random.nextInt(10)) * 10;

        try {
            Thread.sleep(randomNum);
        } catch (InterruptedException e) {
            log.error("sleep error,num", randomNum);
        }

        initAllOnLinePackageSnapshot(null);
    }


    //  todo 初始化所有的mock配置
    public void fetchStrategyMockConfigs() {
    }


    /**
     * @param strategyVersionPkgDTO 从数据库打出来的原始包
     * @param env                   标识测试环境还是线上的环境，是线上来决策还是变更了版本测试使用
     */
    public void reloadPackageSnapshot(final StrategyVersionPkgDTO strategyVersionPkgDTO, Integer env) {
        // 初始化规则
        RuleFlow ruleFlow = new RuleFlow();
        // 填充策略id等属性
        BeanUtils.copyProperties(strategyVersionPkgDTO, ruleFlow);
        // 填充规则流相关属性
        BeanUtils.copyProperties(strategyVersionPkgDTO.getRuleFlow(), ruleFlow);

        // 设置依赖输入变量
        ruleFlow.setInputField(strategyVersionPkgDTO.getInputFields());
        // 设置依赖事件变量
        ruleFlow.setEventField(strategyVersionPkgDTO.getEventFields());
        // 设置输出依赖事件变量
        loadOutputFields(ruleFlow, strategyVersionPkgDTO.getOutputFields());
        // 设置中间变量
        loadMidFields(ruleFlow, strategyVersionPkgDTO.getMidFields());


        // 拿到当前策略版本对应的主规则流下所有的规则节点
        Map<String, RuleFlowNodeSnapshotDTO> ruleFlowNodeSnapshots = strategyVersionPkgDTO.getRuleFlow().getNodes();

        // 解析出所有的规则流节点，key为nodeId,value为对应的节点类型
        Map<Long, BaseNode> nodes = reloadNodes(ruleFlow, ruleFlowNodeSnapshots, strategyVersionPkgDTO);

        // 初始化开始节点
        NormalNode startNode = new NormalNode();
        RuleFlowNodeSnapshotDTO startNodeDTO = strategyVersionPkgDTO.getRuleFlow().getStartNode();
        BeanUtils.copyProperties(startNodeDTO, startNode);
        ruleFlow.setStartNode(startNode);

        // 遍历所有的节点开始建立节点之间的执行关系
        setStartNextRelation(startNodeDTO, startNode, ruleFlowNodeSnapshots, nodes);

        ruleFlow.setNodes(nodes);

        // todo 接入redis将包缓存起来,当前用本地map
        if (DeployEnvEnum.TEST.getCode().equals(strategyVersionPkgDTO.getEnv())) {
            offlineSnapshotCacheLogic.put(strategyVersionPkgDTO.getStrategyCode() + "_" + strategyVersionPkgDTO.getStrategyVersionId(), ruleFlow);
        } else {
            onlineSnapshotCacheLogic.put(strategyVersionPkgDTO.getStrategyCode() + "_" + strategyVersionPkgDTO.getStrategyVersionId(), ruleFlow);
        }

        // 本地调试简单使用，接入redis后删除
        ruleFlowCache = ruleFlow;
    }


    /**
     * 设置所有依赖的输出变量
     *
     * @param ruleFlow
     * @param outputFields
     */
    private void loadOutputFields(RuleFlow ruleFlow, List<DecisionFieldDTO> outputFields) {

        HashMap<String, DecisionFieldDTO> outputFieldMaps = new HashMap<>();
        if (!CollectionUtils.isEmpty(outputFields)) {
            for (DecisionFieldDTO outputField : outputFields) {
                outputFieldMaps.put(outputField.getFieldName(), outputField);
            }
        }

        ruleFlow.setOutputFieldMaps(outputFieldMaps);
    }

    /**
     * 设置所有的中间变量
     *
     * @param ruleFlow
     * @param midFields
     */
    private void loadMidFields(RuleFlow ruleFlow, List<DecisionFieldDTO> midFields) {

        HashMap<String, DecisionFieldDTO> midFieldMaps = new HashMap<>();
        if (CollectionUtils.isEmpty(midFields)) {
            for (DecisionFieldDTO midField : midFields) {
                midFieldMaps.put(midField.getFieldName(), midField);
            }
        }

        ruleFlow.setMidFieldMaps(midFieldMaps);
    }


    /**
     * 解析出所有的规则流节点，key为nodeId,value为对应的节点类型
     *
     * @param ruleFlow              当是主规则流的时候是策略下主规则流，当是子规则流调用当前方法的时候是父规则流
     * @param ruleFlowNodeSnapshots 主规则流下的所有节点
     * @param strategyVersionPkgDTO 主策略版本包
     * @return
     */
    private Map<Long, BaseNode> reloadNodes(RuleFlow ruleFlow, Map<String, RuleFlowNodeSnapshotDTO> ruleFlowNodeSnapshots, StrategyVersionPkgDTO strategyVersionPkgDTO) {

        HashMap<Long, BaseNode> nodes = new HashMap<>();
        for (Map.Entry<String, RuleFlowNodeSnapshotDTO> ruleFlowNodeSnapshotPare : ruleFlowNodeSnapshots.entrySet()) {

            RuleFlowNodeSnapshotDTO flowNodeSnapshot = ruleFlowNodeSnapshotPare.getValue();

            // 如果是结束节点和开始节点
            if (flowNodeSnapshot.getNodeType().equals(RuleFlowNodeTypeEnum.END.getCode())
                    || flowNodeSnapshot.getNodeType().equals(RuleFlowNodeTypeEnum.MERGE.getCode())
            ) {
                NormalNode normalNode = new NormalNode();
                BeanUtils.copyProperties(flowNodeSnapshot, normalNode);
                nodes.put(normalNode.getNodeId(), normalNode);
            }

            // 如果是子规则流节点,则解析下面的节点
            else if (flowNodeSnapshot.getNodeType().equals(RuleFlowNodeTypeEnum.NEST.getCode())) {

                SubRuleFlowNode subRuleFlowNode = new SubRuleFlowNode();
                BeanUtils.copyProperties(flowNodeSnapshot, subRuleFlowNode);

                // 子规则流所有的节点信息
                RuleFlowSnapshotDTO ruleFlowSnapshotDTO = strategyVersionPkgDTO.getSubRuleFlowMaps().get(flowNodeSnapshot.getRuleId());
                // 解析所有的子规则流节点信息
                RuleFlow subRuleFlow = reloadSubRuleFlow(ruleFlow, ruleFlowSnapshotDTO, strategyVersionPkgDTO);
                subRuleFlowNode.setRuleFlow(subRuleFlow);
                log.info("subRuleFlowNode destruct ruleFlowSnapshotDTO ={} ||| ruleFlow={} ", ruleFlowSnapshotDTO, subRuleFlow);

                nodes.put(subRuleFlowNode.getNodeId(), subRuleFlowNode);
            }

            // 如果是执行节点
            else if (flowNodeSnapshot.getNodeType().equals(RuleFlowNodeTypeEnum.EXECUTE.getCode())) {
                ExecuteNode executeNode = new ExecuteNode();
                BeanUtils.copyProperties(flowNodeSnapshot, executeNode);

                // 如果是aciviator类型的规则，则需要编译好aciviator对象
                if (flowNodeSnapshot.getRuleInfo().getExecuteType().equals(ScriptTypeEnum.Aviator.getCode())

                ) {
                    ExpressionRule expressionRule = new ExpressionRule();
                    BeanUtils.copyProperties(flowNodeSnapshot.getRuleInfo(), expressionRule);
                    expressionRule.compile();
                    executeNode.setRuleInfo(expressionRule);
                }
                // 如果是groovy类型的规则，则需要编译好groovy对象
                else if (flowNodeSnapshot.getRuleInfo().getExecuteType().equals(ScriptTypeEnum.Groovy.getCode())
                        || flowNodeSnapshot.getRuleInfo().getExecuteType().equals(ScriptTypeEnum.ScoreCard.getCode())
                ) {
                    ScriptRule scriptRule = new ScriptRule();
                    BeanUtils.copyProperties(flowNodeSnapshot.getRuleInfo(), scriptRule);
                    scriptRule.compile();
                    executeNode.setRuleInfo(scriptRule);
                }
                log.info("ruleNode snapshot= {},init exceuteNode= {}", flowNodeSnapshot, executeNode);
                nodes.put(executeNode.getNodeId(), executeNode);
            }

            // 如果是分支节点
            else if (flowNodeSnapshot.getNodeType().equals(RuleFlowNodeTypeEnum.BRANCH.getCode())) {
                BranchNode branchNode = new BranchNode();
                BeanUtils.copyProperties(flowNodeSnapshot, branchNode);

                // 将分支下面的节点解析出来，并且编译好分支判断逻辑
                ArrayList<NextNode> nextNodes = new ArrayList<>();
                for (NextNodesDTO nextNodesDTO : flowNodeSnapshot.getNextNodes()) {
                    NextNode nextNode = new NextNode();
                    nextNode.setExpression(nextNodesDTO.getExpression());
                    nextNodes.add(nextNode);
                }
                branchNode.setNextNodes(nextNodes);
                branchNode.compile();

                nodes.put(branchNode.getNodeId(), branchNode);
            }

        }

        return nodes;
    }

    /**
     * 解析子规则流的规则
     *
     * @param parentRuleFlow        父规则流
     * @param ruleFlowSnapshotDTO   当前规则所有节点
     * @param strategyVersionPkgDTO 策略版本包
     * @return
     */
    private RuleFlow reloadSubRuleFlow(RuleFlow parentRuleFlow, RuleFlowSnapshotDTO ruleFlowSnapshotDTO, StrategyVersionPkgDTO strategyVersionPkgDTO) {
        RuleFlow subRuleFlow = new RuleFlow();
        subRuleFlow.setId(ruleFlowSnapshotDTO.getId());
        subRuleFlow.setFlowName(ruleFlowSnapshotDTO.getFlowName());
        subRuleFlow.setParentFlowId(ruleFlowSnapshotDTO.getParentFlowId());
        subRuleFlow.setInputField(parentRuleFlow.getInputField());
        subRuleFlow.setOutputFieldMaps(parentRuleFlow.getOutputFieldMaps());
        // 设置输出变量列表
        subRuleFlow.setMidFieldMaps(parentRuleFlow.getMidFieldMaps());

        Map<String, RuleFlowNodeSnapshotDTO> ruleFlowSnapshotNodes = ruleFlowSnapshotDTO.getNodes();
        // 拿到子规则流下面所有的节点
        Map<Long, BaseNode> nodes = reloadNodes(parentRuleFlow, ruleFlowSnapshotNodes, strategyVersionPkgDTO);

        // 初始化开始节点
        NormalNode startNode = new NormalNode();
        RuleFlowNodeSnapshotDTO startNodeDTO = ruleFlowSnapshotDTO.getStartNode();
        BeanUtils.copyProperties(startNodeDTO, startNode);
        subRuleFlow.setStartNode(startNode);

        // 遍历所有的节点建立关联关系
        setStartNextRelation(startNodeDTO, startNode, ruleFlowSnapshotNodes, nodes);

        // 节点列表和规则流关联
        subRuleFlow.setNodes(nodes);

        return subRuleFlow;
    }

    /**
     * 所有节点执行关系绑定
     *
     * @param startNodeDTO          当前节点的详细信息
     * @param startNode             需要真正使用到的节点
     * @param ruleFlowSnapshotNodes 当前规则流信息
     * @param nodes                 所有的节点信息
     */
    private void setStartNextRelation(RuleFlowNodeSnapshotDTO startNodeDTO, BaseNode startNode,
                                      Map<String, RuleFlowNodeSnapshotDTO> ruleFlowSnapshotNodes, Map<Long, BaseNode> nodes) {

        // 如果开始节点下面的节点不为空,先建立开始节点关系
        if (!CollectionUtils.isEmpty(startNodeDTO.getNextNodes())) {
            List<NextNodesDTO> nextNodesDTOS = startNodeDTO.getNextNodes();
            ArrayList<NextNode> nextNodes = new ArrayList<>();

            for (int i = 0; i < nextNodesDTOS.size(); i++) {
                NextNode nextNode = new NextNode();
                nextNode.setNodeInfo(nodes.get(nextNodesDTOS.get(i).getNodeId()));
                nextNode.setExpression(nextNodesDTOS.get(i).getExpression());
                // 如果下一个节点是分支节点，则需要将分支节点判断逻辑编译
                if (nodes.containsKey(startNodeDTO.getNodeId()) && startNodeDTO.getNodeType().equals(RuleFlowNodeTypeEnum.BRANCH.getCode())) {
                    nextNode.setCompilableInstance(nodes.get(startNodeDTO.getNodeId()).getNextNodes().get(i).getCompilableInstance());
                }
                nextNodes.add(nextNode);
            }
            startNode.setNextNodes(nextNodes);

            // 如果是开始节点，合并节点，执行节点，规则流子节点则继续递归建立下面节点关系,因为这些节点下面只会有一个执行节点，所以默认取第一个即可
            if (startNodeDTO.getNodeType().equals(RuleFlowNodeTypeEnum.START.getCode())
                    || startNodeDTO.getNodeType().equals(RuleFlowNodeTypeEnum.MERGE.getCode())
                    || startNodeDTO.getNodeType().equals(RuleFlowNodeTypeEnum.EXECUTE.getCode())
                    || startNodeDTO.getNodeType().equals(RuleFlowNodeTypeEnum.NEST.getCode())) {

                setStartNextRelation(ruleFlowSnapshotNodes.get(nextNodes.get(0).getNodeInfo().getNodeId().toString()),
                        startNode.getNextNodes().get(0).getNodeInfo(),
                        ruleFlowSnapshotNodes,
                        nodes
                );
            }
            // 如果是分支节点，下面的执行节点有多个所以需要循环递归去建立下面的节点关系
            if (startNodeDTO.getNodeType().equals(RuleFlowNodeTypeEnum.BRANCH.getCode())) {
                for (int i = 0; i < nextNodes.size(); i++) {
                    setStartNextRelation(ruleFlowSnapshotNodes.get(nextNodes.get(i).getNodeInfo().getNodeId().toString()),
                            startNode.getNextNodes().get(i).getNodeInfo(),
                            ruleFlowSnapshotNodes,
                            nodes
                    );
                }
            }
        }
    }


    /**
     * 初始化所有的发布完成的版本
     */
    public void initAllOfflinePackageSnapshot() {
        long start = System.nanoTime();

        DecisionStrategyVersionDeployEntity deployDTO = new DecisionStrategyVersionDeployEntity();
        deployDTO.setStatus(DeployStatusEnum.FINISH.getCode());
        // todo 查询出所有的数据库中的离线版本
        List<DecisionStrategyVersionDeployEntity> offlineDeploys = new ArrayList<>();
        for (DecisionStrategyVersionDeployEntity offlineDeploy : offlineDeploys) {
            // 对数据库中的离线版本进行初始化
            desctructPackageSnapshot(offlineDeploy);
        }

        long cost = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        log.info("initAllOfflinePackageSnapshot,time={}", cost);
    }


    /**
     * 缓存流量配置信息
     */
    public void desctructPackageSnapshot(DecisionStrategyVersionDeployEntity deployDTO) {
        // 如果快照是放在oss上面则从oss上取
        if (!StringUtils.isEmpty(deployDTO.getPackageUrl())) {

        } else {
            // 解析快照文本并且缓存在本地
            StrategyVersionPkgDTO strategyVersionPkgDTO = JSONObject.parseObject(deployDTO.getPackageData(), StrategyVersionPkgDTO.class);
            reloadPackageSnapshot(strategyVersionPkgDTO, deployDTO.getEnv());

            // 如果是旁路测试，则需要解析流量配置，缓存在本地
            if (Objects.nonNull(strategyVersionPkgDTO.getByPathDivisionSnapshotDTO())) {
                onlineBypathDivisionCacheLogic.put(
                        strategyVersionPkgDTO.getStrategyCode()
                        , strategyVersionPkgDTO.getByPathDivisionSnapshotDTO()
                );
            }
        }
    }


    /**
     * 初始化所有流程配置已完成的版本,如果有传入规则编码则单独拉取当前规则编码
     */
    public void initAllOnLinePackageSnapshot(String strategyCode) {
        long start = System.nanoTime();

        DecisionStrategyDivisionEntity queryDivision = new DecisionStrategyDivisionEntity();
        queryDivision.setStatus(StrategyDivisionStatusEnum.FINISH.getCode());

        // 如果策略码不为空则只针对当前策略进行流量配置和包初始化
        if (!StringUtils.isEmpty(strategyCode)) {
            queryDivision.setStrategyCode(strategyCode);
        }

        // todo 查询数据库中当前已经上线的流量配置对应的版本
        List<DecisionStrategyDivisionEntity> divisionDTOS = new ArrayList<>();

        // 遍历每一个策略的分流配置
        for (DecisionStrategyDivisionEntity divisionDTO : divisionDTOS) {
            List<DecisionStrategyDivisionConfigDTO> divisionConfigDTOS = JSONObject.parseArray(divisionDTO.getDivisionContent(), DecisionStrategyDivisionConfigDTO.class);
            // 解析分流配置拉取对应的包
            for (DecisionStrategyDivisionConfigDTO configDTO : divisionConfigDTOS) {

                DecisionStrategyVersionDeployEntity queryDeploy = new DecisionStrategyVersionDeployEntity();
                queryDeploy.setStrategyVersionId(configDTO.getStrategyVersionId());
                queryDeploy.setStatus(DeployStatusEnum.FINISH.getCode());
                // 查询对应的发布版本包
                DecisionStrategyVersionDeployEntity strategyVersionDeployDTO = new DecisionStrategyVersionDeployEntity();
                if (Objects.isNull(strategyVersionDeployDTO)) {
                    throw new RuntimeException("有异常状态版本= " + configDTO.toString());
                }

                // 初始化包
                desctructPackageSnapshot(strategyVersionDeployDTO);
            }

            // 缓存并且生效当前策略下的流量配置
            if (!CollectionUtils.isEmpty(divisionConfigDTOS)) {

                StrategyDivisionSnapshotDTO strategyDivisionConfig = new StrategyDivisionSnapshotDTO();
                strategyDivisionConfig.setDivisionConfigs(divisionConfigDTOS);
                strategyDivisionConfig.setDivisionType(divisionDTO.getDivisionType());

                onlineDivisionCacheLogic.put(divisionConfigDTOS.get(0).getStrategyCode(), strategyDivisionConfig);
            }

        }

        long cost = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        log.info("initAllOnLinePackageSnapshot,time={}", cost);
    }


}
